package com.lcf.framework.utils;

import com.lcf.framework.annotation.DictFile;
import com.lcf.framework.annotation.DictTable;
import com.lcf.framework.exception.SysException;
import com.lcf.framework.utils.oss.OssUtils;
import com.lcf.system.dto.DictTableDTO;
import jakarta.annotation.Resource;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.ConcurrentReferenceHashMap;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 描述：实体类翻译
 *
 * @author lcf
 * @since 2025/4/1 20:17
 */
@Slf4j
@Component
public class EntityTranslateUtils {
    @Resource
    private DictTableUtils dictTableUtils;

    // 弱引用缓存反射字段（类 -> 字段名 -> Field对象）
    private static final Map<Class<?>, Map<String, Field>> FIELD_CACHE =
            new ConcurrentReferenceHashMap<>(16, ConcurrentReferenceHashMap.ReferenceType.WEAK);

    // 线程安全的上下文存储（扫描阶段收集翻译信息）
    private static final ThreadLocal<DictBatchContext> BATCH_CONTEXT = ThreadLocal.withInitial(DictBatchContext::new);

    /**
     * 翻译
     */
    public Object translate(Object result) {
        try {
            // 阶段1：扫描所有需要翻译的字段（字典+表字段）
            scanAndProcess(result, true);

            // 阶段2：批量预加载表翻译缓存
            preloadCacheFromDatabase();

            // 阶段3：统一填充翻译值（表字段+文件）
            scanAndProcess(result, false);
        } finally {
            BATCH_CONTEXT.remove();
            dictTableUtils.clearCache();
        }

        return result;
    }

    /**
     * 判断是否为JDK类
     */
    public boolean isNotJdkClass(Class<?> clazz) {
        return !clazz.getName().startsWith("java.")
                && !clazz.getName().startsWith("javax.")
                && !(clazz.isArray() && clazz.getComponentType() == byte.class);
    }

    /**
     * 统一扫描和处理入口
     *
     * @param obj         处理对象
     * @param isScanPhase 是否为扫描阶段（true=收集主键值，false=填充翻译值）
     */
    private void scanAndProcess(Object obj, boolean isScanPhase) {
        processInternal(obj, new WeakHashMap<>(), isScanPhase);
    }

    /**
     * 递归处理核心逻辑
     */
    private void processInternal(Object obj, Map<Object, Boolean> processedMap, boolean isScanPhase) {
        if (obj == null || processedMap.containsKey(obj)) {
            return;
        }

        //防止递归处理对象时出现循环引用导致的无限循环，并确保每个对象只被处理一次。
        processedMap.put(obj, true);

        // 处理集合类型
        if (obj instanceof Collection<?> collection) {
            collection.forEach(item -> processInternal(item, processedMap, isScanPhase));
            return;
        }

        // 处理数组类型
        if (obj.getClass().isArray()) {
            for (Object item : (Object[]) obj) {
                processInternal(item, processedMap, isScanPhase);
            }
            return;
        }

        // 处理单个对象的字段
        Class<?> clazz = obj.getClass();
        if (isNotJdkClass(clazz)) {
            for (Field field : getDeclaredFields(clazz)) {
                handleField(obj, field, processedMap, isScanPhase);
            }
        }
    }

    /**
     * 统一字段处理逻辑（处理@Dict和@DictTable注解）
     */
    private void handleField(Object obj, Field field, Map<Object, Boolean> processedMap, boolean isScanPhase) {
        try {
            field.setAccessible(true);
            Object fieldValue = field.get(obj);
            if (fieldValue == null) {
                return;
            }

            // 处理@DictTable注解（扫描阶段收集，填充阶段处理）
            if (field.isAnnotationPresent(DictTable.class)) {
                DictTable dictTable = field.getAnnotation(DictTable.class);
                if (isScanPhase) {
                    collectTableKeys(dictTable, fieldValue);
                } else {
                    fillTableValue(obj, dictTable, fieldValue);
                }
            }

            //处理@DictFile注解，将文件相对路径转为绝对路径，只处理 String 类型字段
            if (field.isAnnotationPresent(DictFile.class) && field.getType() == String.class) {
                String filePath = (String) fieldValue;
                if (!isScanPhase) {
                    Map<String, String> absoluteUrlMap = OssUtils.getAbsoluteUrlMap(Collections.singletonList(filePath));
                    field.set(obj, absoluteUrlMap.get(filePath));
                }
            }

            // 递归处理字段值
            processInternal(fieldValue, processedMap, isScanPhase);
        } catch (Exception e) {
            throw new SysException(e);
        }
    }

    /**
     * 扫描阶段：收集表翻译需要的主键值
     */
    private void collectTableKeys(DictTable dictTable, Object pkValue) {
        if (pkValue == null) {
            return;
        }

        // 收集主键值
        BATCH_CONTEXT.get().addTableKey(dictTable, pkValue);
    }

    /**
     * 填充阶段：填充表翻译字段值
     */
    private void fillTableValue(Object obj, DictTable dictTable, Object pkValue) throws Exception {
        if (pkValue == null) {
            return;
        }

        String tableName = dictTable.tableName();
        String primaryKey = dictTable.primaryKey();
        String[] targetFields = dictTable.nameField();

        // 获取缓存值
        String cacheKey = DictTableUtils.buildCacheKey(tableName, primaryKey, pkValue);

        // 填充目标字段
        for (String targetField : targetFields) {
            Field target = getTargetField(obj.getClass(), targetField);
            if (target != null) {
                Object value = dictTableUtils.getTargetValue(cacheKey, targetField);
                if (value != null) {
                    target.set(obj, convertValue(value, target.getType()));
                }
            }
        }
    }

    /**
     * 批量预加载表翻译缓存
     */
    private void preloadCacheFromDatabase() {
        DictBatchContext context = BATCH_CONTEXT.get();
        context.getTableKeys().forEach((tableKey, config) -> dictTableUtils.preCache(config));
    }

    private Field getTargetField(Class<?> clazz, String fieldName) {
        return FIELD_CACHE
                .computeIfAbsent(clazz, k -> new ConcurrentHashMap<>())
                .computeIfAbsent(fieldName, fn -> {
                    try {
                        Field field = clazz.getDeclaredField(fn);
                        field.setAccessible(true);
                        return field;
                    } catch (NoSuchFieldException e) {
                        return null;
                    }
                });
    }

    private List<Field> getDeclaredFields(Class<?> clazz) {
        return Arrays.asList(clazz.getDeclaredFields());
    }

    private Object convertValue(Object value, Class<?> targetType) {
        if (targetType == String.class) {
            return String.valueOf(value);
        } else if (targetType == Integer.class || targetType == int.class) {
            return Integer.parseInt(value.toString());
        } else if (targetType == Long.class || targetType == long.class) {
            return Long.parseLong(value.toString());
        } else if (targetType == Boolean.class || targetType == boolean.class) {
            return Boolean.parseBoolean(value.toString());
        } else if (targetType == Double.class || targetType == double.class) {
            return Double.parseDouble(value.toString());
        } else if (targetType == Float.class || targetType == float.class) {
            return Float.parseFloat(value.toString());
        } else if (targetType == BigDecimal.class) {
            return new BigDecimal(value.toString());
        } else if (targetType == LocalDateTime.class) {
            return LocalDateTime.parse(value.toString());
        } else if (targetType == LocalDate.class) {
            return LocalDate.parse(value.toString());
        }
        // 其他类型转换...
        return value;
    }

    /**
     * 批量上下文存储结构
     */
    @Getter
    private static class DictBatchContext {
        // 表翻译配置（Key格式：tableName#primaryKey）
        private final Map<String, DictTableDTO> tableKeys = new ConcurrentHashMap<>();

        public void addTableKey(DictTable dictTable, Object pkValue) {
            String key = dictTable.tableName() + "#" + dictTable.primaryKey() + "#" + String.join("#", dictTable.nameField());
            tableKeys.computeIfAbsent(key, k -> new DictTableDTO(dictTable))
                    .addId(pkValue);
        }
    }
}
